import { Callout } from "nextra-theme-docs";
import { EffectHookDiagram } from "../../../components/diagrams/EffectHookDiagram";
import { APITable } from "../../../components/APITable";

# 플러그인 작성하기

**Stackflow**는 플러그인 인터페이스를 통해 다른 사람이 작성한 확장 로직을 쉽게 내 어플리케이션과 연동할 수 있도록 도와요. 플러그인을 통해 나의 문제를 해결하고, 이를 예쁘게 감싸서 다른 사람과 공유해보세요.

## 프리셋 만들기

**Stackflow** 플러그인을 출판하는 가장 쉬운 방법은 다른 사람이 만든 플러그인들을 조합해서 프리셋으로 제공하는 것이에요. 다음과 같이 여러 플러그인을 Array로 묶어서 나만의 프리셋을 만들 수 있어요.

```ts showLineNumbers filename="stackflow.ts" copy
import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic";
import { historySyncPlugin } from "@stackflow/plugin-history-sync";
import { stackflow } from "@stackflow/react";

const myPlugin = ({ ... }) => [
  basicRendererPlugin(),
  historySyncPlugin({ ... }),
];

stackflow({
  // ...
  plugins: [myPlugin()],
});
```

## 기본 인터페이스

플러그인은 함수의 형태로 아래와 같은 값을 필수로 반환해야해요.

<APITable>
|      |          |                                                                          |
| ---- | -------- | ------------------------------------------------------------------------ |
| key  | `string` | 플러그인이 배열 형태로 리액트 트리 내에 렌더링 될때 `key` 값으로 부여할 고유한 문자열 값이에요. |
</APITable>

내 앱에 나만의 첫 플러그인 개발을 시도해보려면 다음과 같이 inline 함수로 시작해보세요. (어렵지 않아요!)

```ts showLineNumbers filename="stackflow.ts" copy
import { basicRendererPlugin } from "@stackflow/plugin-renderer-basic";
import { stackflow } from "@stackflow/react";

stackflow({
  // ...
  plugins: [
    basicRendererPlugin(),
    basicUIPlugin({
      theme: "cupertino",
    }),
    () => {
      return {
        key: "my-plugin",
      };
    },
  ],
});
```

### 렌더링 추가하기

렌더링을 새로 추가하고 싶다면, `render` API를 사용할 수 있어요. Stack 상태를 이용해 어떻게 DOM을 최적화할지 결정하고 싶거나, 때에 따라 새로 얹을 새로운 DOM 트리가 필요하다면 `render` API를 활용하세요.

```tsx showLineNumbers filename="stackflow.ts" copy copy
stackflow({
  // ...
  plugins: [
    () => {
      return {
        key: "my-plugin",
        render({ stack }) {
          return (
            <div className="my-rendering">
              {stack.render().activities.map((activity) => (
                <div className="my-activity" key={activity.id}>
                  {activity.render()}
                </div>
              ))}
            </div>
          );
        },
      };
    },
  ],
});
```

다음과 같이 UI에 전달되는 스택 상태를 덮어 쓸 수도 있어요.

```tsx showLineNumbers filename="stackflow.ts" copy {12,17}
stackflow({
  // ...
  plugins: [
    () => {
      return {
        key: "my-plugin",
        render({ stack }) {
          return (
            <div className="my-rendering">
              {stack
                .render({
                  // 여기서 stack 상태를 덮어쓸 수 있어요.
                })
                .activities.map((activity) => (
                  <div className="my-activity" key={activity.id}>
                    {activity.render({
                      // 여기서 activity 상태를 덮어쓸 수 있어요.
                    })}
                  </div>
                ))}
            </div>
          );
        },
      };
    },
  ],
});
```

<Callout emoji="💡">
  덮어써진 상태값은 메인 스택에는 영향을 주지 않고, 해당 렌더링 하위의 React 의
  `useStack()`, `useActivity()`에만 적용돼요.
</Callout>

### 스택 감싸기

최상단에 Context API Provider를 추가하고 싶거나, 최상단을 특정 DOM으로 감싸고 싶다면 `wrapStack` 인터페이스를 활용하세요.

```tsx showLineNumbers filename="stackflow.ts" copy {8}
stackflow({
  // ...
  plugins: [
    () => {
      return {
        key: "my-plugin",
        wrapStack({ stack }) {
          // 여기서 인자로 가져온 stack 정보를 활용할 수 있습니다
          return <div className="my-plugin">{stack.render()}</div>;
        },
      };
    },
  ],
});
```

<Callout type="warning" emoji="⚠️">
  **주의** - `wrapStack` API는 모든 렌더링에 함께 적용돼요. 의도치 않은 사이드
  이펙트가 생길 수 있으니 주의해서 사용해요.
</Callout>

### 액티비티 감싸기

각 액티비티를 Context API Provider를 추가하고 싶거나, 특정 DOM으로 감싸고 싶다면 `wrapActivity` 인터페이스를 활용하세요.

```tsx showLineNumbers filename="stackflow.ts" copy {8}
stackflow({
  // ...
  plugins: [
    () => {
      return {
        key: "my-plugin",
        wrapActivity({ activity }) {
          // 여기서 인자로 가져온 activity 정보를 활용할 수 있습니다
          return <div className="my-activity">{activity.render()}</div>;
        },
      };
    },
  ],
});
```

<Callout type="warning" emoji="⚠️">
  **주의** - `wrapActivity` API는 모든 렌더링에 함께 적용돼요. 의도치 않은
  사이드 이펙트가 생길 수 있으니 주의해서 사용해요.
</Callout>

### 초기화 시점에 동작 주입하기

`<Stack />` 컴포넌트가 최초로 초기화 될 때 로직을 호출하고 싶다면 `onInit` 훅을 사용해요.

```tsx showLineNumbers filename="stackflow.ts" copy {7-9}
stackflow({
  // ...
  plugins: [
    () => {
      return {
        key: "my-plugin",
        onInit() {
          console.log("Initialized!");
        },
      };
    },
  ],
});
```

<Callout type="warning" emoji="⚠️">
  **주의** - React 18과 `React.StrictMode`에서는 두 번 호출 될 수 있으니, 의도치
  않은 부수효과가 없도록 신경써주세요.
</Callout>

## 이펙트 훅

기능을 확장하거나 외부의 상태와 동기화하고 싶으신가요? 스택 상태가 변화할때마다 특정 동작을 하거나, 또는 스택 상태가 변경되기 이전에 함수를 호출할 수 있어요.

스택 상태는 유저의 행동으로 인해, push, replace, pop이 호출됐을때 변화하기 시작해요. 해당 시점부터 스택 상태가 변화하기 전(Pre-effect)과 변화한 뒤 UI까지 반영된 이후 (Post-effect)에 특정 동작을 수행할 수 있어요.

<EffectHookDiagram />

### Post-effects

Post-effect 훅에는 `onPushed`, `onReplaced`, `onPopped`, `onChanged`가 있어요. Post-effect 훅은 UI까지 모두 반영된 이후에 호출되기 때문에, 해당 변화를 다시 돌이키거나 취소할 수 없어요. Post-effect 훅은 다음과 같은 인자를 사용할 수 있어요.

<APITable>
|                       |            |                             |
| --------------------- | ---------- | --------------------------- |
| actions.getStack      | `function` | 현재 스택의 상태를 가져올 수 있어요. |
| actions.dispatchEvent | `function` | 코어에 새 이벤트를 추가해요.       |
| effect                | `object`   | 해당 이펙트 훅을 촉발시킨 이펙트에요. |
</APITable>

```tsx showLineNumbers filename="stackflow.ts" copy
stackflow({
  // ...
  plugins: [
    () => {
      return {
        key: "my-plugin",
        onPushed(actions, effect) {
          // actions.getStack()
          // actions.dispatchEvent(...)
          // 를 활용할 수 있어요
          console.log("Pushed!");
          console.log("Effect:", effect);
        },
      };
    },
  ],
});
```

<Callout emoji="💡">
  `onChanged` 훅은 스택 상태가 변경될때마다 구별없이 모두 호출돼요. 따라서
  `onPushed`, `onReplaced`, `onPopped`와 함께 사용하면, 두 이펙트 훅 모두
  호출돼요.
</Callout>
<Callout type="error" emoji="🚫">
  **경고** - 만약 이펙트 훅 내부에서 해당 이펙트를 촉발하는 이벤트를
  dispatch하는 경우 이펙트 훅과 맞물려서 무한 루프가 돌 수 있어요.
  `actions.dispatchEvent()`를 활용하는 경우 주의해서 사용하세요.
</Callout>

### Pre-effects

Pre-effect 훅에는 `onBeforePush`, `onBeforeReplace`, `onBeforePop`이 있어요. Pre-effect 훅은 아직 Core에 해당 이벤트가 전달되기 전에 호출되기 때문에, 해당 이벤트를 취소할 수 있어요. Pre-effect 훅은 다음과 같은 인자를 사용할 수 있어요.

<APITable>
|                        |            |                               |
| ---------------------- | ---------- | ----------------------------- |
| actions.preventDefault | `function` | (추가) 기본 동작을 취소해요.         |
| actions.getStack       | `function` | G현재 스택의 상태를 가져올 수 있어요.  |
| actions.dispatchEvent  | `function` | 코어에 새 이벤트를 추가해요.         |
| effect                 | `object`   | 해당 이펙트 훅을 촉발시킨 이펙트에요.   |
</APITable>

## 첫 액티비티 결정하기

`initialPushedEvent` API를 통해 기존에 존재하는 `initialActivity` 동작을 덮어쓸 수 있어요.

```tsx showLineNumbers filename="stackflow.ts" copy {1, 9-13}
import { makeEvent } from "@stackflow/core";

stackflow({
  // ...
  plugins: [
    () => {
      return {
        key: "my-plugin",
        initialPushedEvent() {
          return makeEvent("Pushed", {
            // ...
          });
        },
      };
    },
  ],
});
```

